package at.ac.tuwien.ame.validation;

import static com.google.common.base.Objects.firstNonNull;
import static com.google.common.base.Predicates.instanceOf;
import static com.google.common.collect.Collections2.filter;
import static com.google.common.collect.Lists.newArrayList;

import java.util.Collection;
import java.util.Collections;
import java.util.List;


import org.eclipse.emf.common.util.EList;
import org.eclipse.xtext.validation.Check;

import com.google.common.collect.Collections2;
import com.google.common.collect.HashMultiset;
import com.google.common.collect.Iterables;
import com.google.common.collect.LinkedHashMultiset;
import com.google.common.collect.Multiset;

import ftldsl.Assignment;
import ftldsl.For;
import ftldsl.FtldslPackage;
import ftldsl.Function;
import ftldsl.If;
import ftldsl.ReturnStatement;
import ftldsl.Statement;
import ftldsl.Transformation;
import ftldsl.Variable;

public class FTLJavaValidator extends AbstractFTLJavaValidator {
	@Check
	public void checkMethodUnique(Transformation t) {
		Multiset<String> names = HashMultiset.create(Iterables.transform(t.getFunctions(), new com.google.common.base.Function<Function, String>() {
			@Override
			public String apply(Function input) {
				return input.getName();
			}
		}));

		for (Function f : t.getFunctions()) {
			if (names.count(f.getName()) > 1) {
				error("Function '" + f.getName() + "' already defined.", FtldslPackage.Literals.TRANSFORMATION__FUNCTIONS);
			}
		}
	}

	@Check
	public void checkFunctionWithStatements(Function f) {
		if (f.getStatements().isEmpty()) {
			warning("Function '" + f.getName() + "' should contain statements.", FtldslPackage.Literals.FUNCTION__STATEMENTS);
		}
	}

	@Check
	public void checkReturnStatements(Function f) {
		for (int i = 0; i < f.getStatements().size() - 1; i++) {
			if (f.getStatements().get(i) instanceof ReturnStatement) {
				for (int j = i + 1; j < f.getStatements().size(); j++) {
					error("Unreachable statement", f.getStatements().get(j), null, j);
				}
			}
		}
	}

	@Check
	@SuppressWarnings("unchecked")
	public void checkVariableScope(Function f) {
		List<? extends Statement> statements = getStatements(f.getStatements());
		List<? extends Variable> vars = newArrayList((Collection<Variable>) filter(
				statements, instanceOf(Variable.class)));
		if (vars.isEmpty()) {
			return;
		}

		Collection<String> varNames = Collections2.transform(vars, VarToString.INSTANCE);

		LinkedHashMultiset<String> names = LinkedHashMultiset.create(varNames);
		names.add(f.getInput().getName());

		for (int i = 0; i < statements.size(); i++) {
			if (statements.get(i) instanceof Variable) {
				Variable var = (Variable) statements.get(i);
				if (names.count(var.getName()) > 1) {
					String msg = String.format("Variable '%s' already defined in scope.", var.getName());
					error(msg, var, FtldslPackage.Literals.VARIABLE__NAME, i);
				}
			}
		}
	}

	@Check
	@SuppressWarnings("unchecked")
	public void checkReassignValue(Function f) {
		List<? extends Statement> stmts = getStatements(f.getStatements());
		Collection<? extends Assignment> assignments = (Collection<? extends Assignment>) filter(stmts, instanceOf(Assignment.class));
		if (assignments.isEmpty()) {
			return;
		}

		Collection<? extends Variable> vars = (Collection<? extends Variable>) filter(stmts, instanceOf(Variable.class));
		List<String> varNames = newArrayList(Collections2.transform(vars, VarToString.INSTANCE));
		varNames.add(f.getInput().getName());

		for (int i = 0; i < stmts.size(); i++) {
			if (stmts.get(i) instanceof Assignment) {
				Assignment assignment = (Assignment) stmts.get(i);
				if (assignment.getOperator().equals("=") && varNames.contains(assignment.getModelVariable().getName())) {
					error(String.format("Cannot reassign value '%s'", assignment.getModelVariable().getName()), assignment, FtldslPackage.Literals.ASSIGNMENT__ASSIGNMENT, i);
				}
			} else if (stmts.get(i) instanceof For) {
				For loop = (For) stmts.get(i);
				if (varNames.contains(loop.getIterator())) {
					error(String.format("Cannot reassign value '%s'", loop.getIterator()), loop, FtldslPackage.Literals.FOR__ITERATOR, i);
				}
			}
		}
	}

	private List<? extends Statement> getStatements(EList<Statement> stmts) {
		List<Statement> list = newArrayList();
		for (Statement stmt : firstNonNull(stmts, Collections.<Statement>emptyList())) {
			list.add(stmt);
			if (stmt instanceof For) {
				For f = (For) stmt;
				list.addAll(getStatements(f.getBody()));
			} else if (stmt instanceof If) {
				If i = (If) stmt;
				list.addAll(getStatements(i.getLeft()));
				list.addAll(getStatements(i.getRight()));
			}
		}
		return list;
	}

	static class VarToString implements com.google.common.base.Function<Variable, String> {
		public static final VarToString INSTANCE = new VarToString();

		@Override
		public String apply(Variable input) {
			return input.getName();
		}
	}
}
